#pragma once
#include <vector>
#include <map>
#include <set>

/**
 * Below this weight threshold, animations won't be blended in.
 */
#define ANIMWEIGHT_THRESH (0.00001f)  

//
// NameIndexMap
//
typedef std::map<std::string, WORD> NameIndexMap;

//
// KeyFrame
//
struct KeyFrame
{
	float Time;
	Vector3 Translate;
	Quaternion Orientation;
	Vector3 Scale;

	void Blend(const KeyFrame& k0, const KeyFrame& k1, float alpha)
	{
		if (alpha <= ANIMWEIGHT_THRESH)
			*this = k0;
		else if (alpha >= 1.f - ANIMWEIGHT_THRESH)
			*this = k1;
		else
		{
			Translate = Lerp(k0.Translate, k1.Translate, alpha);
			Scale = Lerp(k0.Scale, k1.Scale, alpha);
			Orientation	= LerpQuat(k0.Orientation, k1.Orientation, alpha);
			Orientation.Normalize();
		}
	}
};
typedef std::vector<KeyFrame> KeyFrameVector;

//
// AnimationTrack
//
struct AnimationTrack
{
	WORD BoneIndex;
	KeyFrameVector KeyFrames;

	void Interprete(float time, KeyFrame& keyFrame) const
	{
		if (KeyFrames.empty())
			return;

		if (KeyFrames.size() == 1 || time <= KeyFrames.front().Time)
			keyFrame = KeyFrames.front();
		else if (time >= KeyFrames.back().Time)
			keyFrame = KeyFrames.back();
		else
		{
			size_t left = 0, right = KeyFrames.size() - 1;
			while (left < right - 1)
			{
				size_t middle = (left + right) / 2;
				const KeyFrame& key = KeyFrames[middle];
				if (key.Time < time)
					left = middle;
				else
					right = middle;
			}

			const KeyFrame& k0 = KeyFrames[left];
			const KeyFrame& k1 = KeyFrames[right];
			float alpha = (time - k0.Time) / (k1.Time - k0.Time);
			keyFrame.Blend(k0, k1, alpha);
		}
	}
};
typedef std::vector<AnimationTrack> AnimationTrackVector;

//
// Animation
//
struct Animation
{
	std::string Name;
	float Length;
	AnimationTrackVector Tracks;
};
typedef std::map<std::string, Animation> AnimationMap;

//
// AnimationState
//
struct AnimationState
{
	const Animation* Anim;
	float TimePosition;
	float Length;
	float Weight;
	bool Enabled;
	bool Loop;

	AnimationState() {}
	AnimationState(const Animation* anim) 
		: Anim(anim)
		, TimePosition(0)
		, Length(anim->Length)
		, Weight(1.0f)
		, Enabled(false)
		, Loop(true)
	{}
};
typedef std::map<std::string, AnimationState> AnimationStateMap;
typedef std::set<AnimationState*> AnimationStateSet;

//
// AnimationStateCollection
//
class AnimationStateCollection
{
public:
	AnimationStateCollection(const AnimationMap& animationMap) 
		: mAnimations(animationMap)
	{
		for (AnimationMap::const_iterator it(animationMap.begin()), end(animationMap.end()); it != end; it++)
			mAnimationStates.insert(AnimationStateMap::value_type(it->first, AnimationState(&it->second)));
	}

	bool EnableAnim(const std::string& name, bool loop)
	{
		AnimationStateMap::iterator it = mAnimationStates.find(name);
		if (it == mAnimationStates.end())
			return false;

		AnimationState& animationState = it->second;
		if (animationState.Enabled)
			return false;

		animationState.Enabled = true;
		animationState.Loop = loop;

		mEnabledAnimations.insert(&animationState);
		return true;
	}

	bool Update(float elapsedTime)
	{
		bool animationDirty = false;
		for (AnimationStateSet::iterator it(mEnabledAnimations.begin()), end(mEnabledAnimations.end()); it != end; it++)
		{
			AnimationState* animState = *it;
			float timePos = animState->TimePosition + elapsedTime;
			if (animState->Loop)
			{
				// Wrap
				timePos = fmod(timePos, animState->Length);
				if (timePos < 0)
					timePos += animState->Length;     
			}
			else
				timePos = Clamp(timePos, 0.0f, animState->Length);

			if (timePos != animState->TimePosition)
			{
				animState->TimePosition = timePos;
				animationDirty = true;
			}
		}
		return animationDirty;
	}

	const AnimationStateSet& getEnabledAnimations() const { return mEnabledAnimations; }

private:
	const AnimationMap& mAnimations;
	AnimationStateMap mAnimationStates;
	AnimationStateSet mEnabledAnimations;
};

